﻿using System.Net;
using System.Net.Sockets;

namespace ClassLibrary_fqy_NET.SocketHelper
{
    public class TcpSeverAsync
    {
        private int m_numConnections;   // 同时处理的最大连接数
        private int m_receiveBufferSize;// 用于每个套接字I/O操作的缓冲区大小
        private BufferManager? m_bufferManager;  // 表示用于所有套接字操作的大型可重用缓冲区集
        private const int opsToPreAlloc = 2;    // 读、写（不为accepts分配缓冲区空间）
        private Socket? listenSocket;            // 用于侦听传入连接请求的套接字
        private SocketAsyncEventArgsPool? m_readWritePool;//用于写入、读取和接受套接字操作的可重用SocketAsyncEventArgs对象池
        private int m_totalBytesRead;           // 服务器接收的总字节数的计数器
        private int m_numConnectedSockets;      // 连接到服务器的客户端总数
        private Semaphore? m_maxNumberAcceptedClients;
        private List<UserTokenAsync>? m_clients; //客户端列表  


        // 定义委托  
        public delegate void OnClientNumberChange(int clientNumber, UserTokenAsync token);//客户端连接数量变化时触发  
        public delegate void OnReceiveData(UserTokenAsync token, byte[] buff);//接收到客户端的数据 
        //定义事件
        public event OnClientNumberChange? ClientNumberChange;
        public event OnReceiveData? ReceiveClientData;

        /// <summary>  
        /// 获取客户端列表  
        /// </summary>  
        public List<UserTokenAsync> ClientList { get { return m_clients!; } }
        /// <summary>
        /// 连接到服务器的客户端总数
        /// </summary>
        public int NumConnectedSockets { get { return m_numConnectedSockets; } }

        /// <summary>
        /// 创建一个未初始化的服务器实例。
        /// 要启动侦听连接请求的服务器，请调用Init方法，然后调用start方法
        /// </summary>
        /// <param name="numConnections">最大连接数</param>
        /// <param name="receiveBufferSize">套接字I/O操作的缓冲区大小</param>
        public TcpSeverAsync(int numConnections, int receiveBufferSize)
        {
            m_totalBytesRead = 0;
            m_numConnectedSockets = 0;
            m_numConnections = numConnections;
            m_receiveBufferSize = receiveBufferSize;
            //分配缓冲区，使最大数量的套接字可以同时具有一个未完成的读取和一个套接字
            m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc, receiveBufferSize);
            m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
            m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
        }
        /// <summary>
        /// 通过预先分配可重复使用的缓冲区和对象来初始化服务器。
        /// 这些对象不需要预先分配或重用，但这样做是为了说明如何轻松地使用API创建可重用对象以提高服务器性能。
        /// </summary>
        public void Init()
        {
            // 分配一个所有I/O操作都使用的大字节缓冲区。这种华丽的碎片
            m_bufferManager!.InitBuffer();
            //初始化用户令牌
            m_clients = new List<UserTokenAsync>();
            // 预分配SocketAsyncEventArgs对象的池
            SocketAsyncEventArgs readWriteEventArg;
            for (int i = 0; i < m_numConnections; i++)
            {
                //预先分配一组可重复使用的SocketAsyncEventArgs
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                readWriteEventArg.UserToken = new UserTokenAsync();
                // 将缓冲池中的字节缓冲区分配给SocketAsyncEventArg对象
                m_bufferManager.SetBuffer(readWriteEventArg);
                // 将SocketAsyncEventArg添加到池中
                m_readWritePool!.Push(readWriteEventArg);
            }
        }
        /// <summary>
        /// 启动服务器，使其侦听传入的连接请求
        /// </summary>
        /// <param name="localEndPoint">服务器将侦听连接请求的端点</param>
        public void Start(IPEndPoint localEndPoint)
        {
            m_clients!.Clear();
            // 创建侦听传入连接的套接字
            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(localEndPoint);
            // 使用100个连接的侦听囤积启动服务器
            listenSocket.Listen(100);
            // 在监听异步接受
            SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();
            acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            StartAccept(acceptEventArg);
        }
        /// <summary>
        /// 开始接受客户端的连接请求的操作
        /// </summary>
        /// <param name="acceptEventArg">在服务器的侦听套接字上发出接受操作时要使用的上下文对象</param>
        private void StartAccept(SocketAsyncEventArgs acceptEventArg)
        {
            // 方法同步完成时循环
            bool willRaiseEvent = false;
            while (!willRaiseEvent)
            {
                m_maxNumberAcceptedClients!.WaitOne();
                // 套接字必须被清除，因为上下文对象正在被重用
                acceptEventArg.AcceptSocket = null;
                willRaiseEvent = listenSocket!.AcceptAsync(acceptEventArg);
                if (!willRaiseEvent)
                {
                    ProcessAccept(acceptEventArg);
                }
            }
        }
        /// <summary>
        /// 此方法是与Socket关联的回调方法。AcceptAsync分配并在接受操作完成时调用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void AcceptEventArg_Completed(object? sender, SocketAsyncEventArgs e)
        {
            //处理接受的连接
            ProcessAccept(e);
            // 接受下一个连接请求
            StartAccept(e);
        }
        /// <summary>
        /// 处理连接
        /// </summary>
        /// <param name="e"></param>
        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            Interlocked.Increment(ref m_numConnectedSockets);
            //获取接受的客户端连接的套接字，并将其放入对象用户令牌中
            SocketAsyncEventArgs readEventArgs = m_readWritePool!.Pop();
            //readEventArgs.UserToken = e.AcceptSocket;
            UserTokenAsync userToken = (UserTokenAsync)readEventArgs.UserToken!;
            userToken.mySocket = e.AcceptSocket;
            userToken.ConnectTime = DateTime.Now;
            userToken.Remote = e.AcceptSocket!.RemoteEndPoint;
            userToken.IP_Client = ((IPEndPoint)e.AcceptSocket.RemoteEndPoint!).Address;
            lock (m_clients!)
            {
                m_clients.Add(userToken);
            }
            if (ClientNumberChange != null)
            {
                ClientNumberChange(1, userToken);
            }
            // 连接客户端后，立即向连接发布接收
            bool willRaiseEvent = e.AcceptSocket!.ReceiveAsync(readEventArgs);
            if (!willRaiseEvent)
            {
                ProcessReceive(readEventArgs);
            }
        }
        /// <summary>
        /// 只要在套接字上完成接收或发送操作，就会调用此方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">与已完成的接收操作关联的SocketAsyncEventArg</param>
        /// <exception cref="NotImplementedException"></exception>
        private void IO_Completed(object? sender, SocketAsyncEventArgs e)
        {
            //确定刚完成的操作类型并调用关联的处理程序
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                default:
                    break;
            }
        }
        /// <summary>
        /// 当异步接收操作完成时，会调用此方法。
        /// 如果远程主机关闭了连接，则套接字关闭。
        /// 如果接收到数据，则将数据回显到客户端。
        /// </summary>
        /// <param name="readEventArgs"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            UserTokenAsync token = (UserTokenAsync)e.UserToken!;
            // 检查远程主机是否关闭了连接
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                //增加服务器接收的总字节数
                Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
                //读取数据
                byte[] data = new byte[e.BytesTransferred];
                Array.Copy(e.Buffer!, e.Offset, data, 0, e.BytesTransferred);
                if (ReceiveClientData != null)
                {
                    ReceiveClientData(token, data);
                }

                // 读取从客户端发送的下一个数据块
                bool willRaiseEvent = token.mySocket!.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                CloseClientSocket(e);
            }
        }
        /// <summary>
        /// 当异步发送操作完成时调用此方法。
        /// 方法在套接字上发出另一个接收，以读取从客户端发送的任何其他数据
        /// </summary>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                // 已将数据回显到客户端
                Socket socket = (Socket)e.UserToken!;
                // 读取从客户端发送的下一个数据块
                bool willRaiseEvent = socket.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                CloseClientSocket(e);
            }
        }
        /// <summary>
        /// 关闭连接
        /// </summary>
        /// <param name="e"></param>
        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            UserTokenAsync? token = e.UserToken as UserTokenAsync;
            lock (m_clients!)
            {
                m_clients.Remove(token!);
            }
            //如果有事件,则调用事件,发送客户端数量变化通知  
            if (ClientNumberChange != null)
            {
                ClientNumberChange(-1, token!);
            }
            // 关闭与客户端关联的套接字
            try
            {
                token!.mySocket!.Shutdown(SocketShutdown.Send);
            }
            // 如果客户端进程已经关闭，则引发
            catch (Exception) { }
            token!.mySocket!.Close();
            // 递减计数器以跟踪连接到服务器的客户端总数
            Interlocked.Decrement(ref m_numConnectedSockets);
            // 释放SocketAsyncEventArg，以便其他客户端可以重用它们
            e.UserToken = new UserTokenAsync();
            m_readWritePool!.Push(e);
            m_maxNumberAcceptedClients!.Release();
        }
        /// <summary>  
        /// 对数据进行打包,然后再发送  
        /// </summary>  
        /// <param name="token"></param>  
        /// <param name="message"></param>  
        /// <returns></returns>  
        public void SendMessage(UserTokenAsync token, byte[] message)
        {
            if (token == null || token.mySocket == null || !token.mySocket.Connected)
            {
                return;
            }
            //新建异步发送对象, 发送消息  
            SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();
            sendArg.UserToken = token;
            sendArg.SetBuffer(message, 0, message.Length);  //将数据放置进去.  
            token.mySocket.SendAsync(sendArg);
        }
    }

}
